package com.aviator.detective.script.aviator;

import com.aviator.detective.common.util.SHA256Util;
import com.aviator.detective.common.util.TypeUtil;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.AviatorEvaluatorInstance;
import com.googlecode.aviator.Expression;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

public abstract class AbstractAviatorScriptExecute {

    private static final int DEFAULT_LRU_EXPRESSION_CACHE_SIZE = 100;
    private AviatorResult aviatorResult = null;
    private  Map<String,Object> env = new HashMap<>();
    public AbstractAviatorScriptExecute(Map<String,Object> env){
        this.env = env;
    }

    /**
     * 初始化AviatorEvaluatorInstance，设置相关参数
     * @return
     */
    public abstract AviatorEvaluatorInstance init();

    /**
     * 编译
     * @param script
     * @return
     */
    private Expression compiler(String script){
        script = replaceEscapeCharacter(script);
        AviatorEvaluatorInstance instance = null;
        Expression expression = null;
        try{
            instance = init();
        }catch (Exception e){
            e.printStackTrace();
            aviatorResult = AviatorResult.initError(null);
            return null;
        }
        try{
            AviatorEvaluator.validate(script);
        }catch (Exception e){
            e.printStackTrace();
            aviatorResult = AviatorResult.validateFailed(null);
            return null;
        }
        //默认初始化
        if (instance == null){
            instance = AviatorEvaluator.getInstance();
            instance.useLRUExpressionCache(DEFAULT_LRU_EXPRESSION_CACHE_SIZE);
        }
        try{
            expression = instance.compile(getCacheKey(script),script,true);
        }catch (Exception e){
            e.printStackTrace();
            aviatorResult = AviatorResult.compilerError(null);
            return null;
        }
        return expression;
    }

    public <T> AviatorResult<Boolean> executeBoolean(String script){
        return execute(script,Boolean.class);
    }

    public <T> AviatorResult<String> executeString(String script){
        return execute(script,String.class);
    }

    public <T> AviatorResult<Long> executeLong(String script){
        return execute(script,Long.class);
    }

    public <T> AviatorResult<BigDecimal> executeDecimal(String script){
        return execute(script,BigDecimal.class);
    }

    public <T> AviatorResult<T> execute(String script,Class<T> destType){
        if (aviatorResult!=null){
            return aviatorResult;
        }
        Expression expression = compiler(script);
        try{
            Object data = expression.execute(env);
            T t = transToDestType(data,destType);
            return AviatorResult.success(t);
        }catch (Exception e){
            e.printStackTrace();
            aviatorResult = AviatorResult.validateFailed(null);
        }
        return aviatorResult;
    }

    private <T> T transToDestType(Object data,Class<T> destType) {
        if (data == null){
            return null;
        }
        if (data.getClass().equals(Object.class)){
            return (T)data;
        }
        if (data.getClass().equals(destType)){
            return (T)data;
        }
        try {
            data = TypeUtil.aviatorResultTypeConvert(data,destType);
            return (T)data;
        }catch (Exception e){
            aviatorResult = AviatorResult.castError(null);
            return null;
        }
    }

    /**
     * sha256算法获取cacheKey
     * @param script
     * @return
     */
    public String getCacheKey(String script){
        return SHA256Util.getSHA256(script);
    }

    public static String replaceEscapeCharacter(String script){
        if (script == null || script == ""){
            return script;
        }
        if (script.contains("\\\\")){
            script = script.replaceAll("\\\\","\\");
        }
        if (script.contains("\\n")){
            script = script.replaceAll("\\n","\n");
        }
        if (script.contains("\\t")){
            script = script.replaceAll("\\t","\t");
        }
        if (script.contains("\\r")){
            script = script.replaceAll("\\r","\r");
        }
        return script;
    }
}
